package com.ccnu.demojava.qtpmessage;

import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;

public class QtpMessage {
    // OptionId
    public static final int kOptionId_Reserved = 60000;     // for system event option
    public static final int kOptionId_SessionId = 60001;     // user data option
    public static final int kOptionId_PageInfo = 60002;     // 为了消息分包消息使用，结构为 PageInfo
    public static final int kOptionId_SubscribeKey = 60003;  // 为了订阅的消息使用
    public static final int kOptionId_ServerTime = 60004;    // 服务器时间，秒
    public static final int kOptionId_ReqId = 60005;         // 请求ID
    public static final int kOptionId_UserToken = 600011;    // 用户token
    public static final int kOptionId_KLinePeriodType = 600020;      // k线周期
    public static final int kOptionId_Extended = 61000;      // for third-party system module

    // MSG_VER
    public static final int QTP_VER = 0;
    public static final int PB_VER = 1;
    public static final int NEVER_USERD = 0x6A;  //大于这个的给交易组用

    // 消息头
    public static class MsgHead {
        private byte version = 0;
        private byte service = 0;
        private int msgtype = 0;
        private int topic = 0;
        private int optslen = 0;
        private long datalen = 0;

        public byte getVersion() {
            return version;
        }

        public void setVersion(byte version) {
            this.version = version;
        }

        public byte getService() {
            return service;
        }

        public void setService(byte service) {
            this.service = service;
        }

        public int getMsgtype() {
            return msgtype;
        }

        public void setMsgtype(int msgtype) {
            this.msgtype = msgtype;
        }

        public int getTopic() {
            return topic;
        }

        public void setTopic(int topic) {
            this.topic = topic;
        }

        public int getOptslen() {
            return optslen;
        }

        public void setOptslen(int optslen) {
            this.optslen = optslen;
        }

        public long getDatalen() {
            return datalen;
        }

        public void setDatalen(long datalen) {
            this.datalen = datalen;
        }

        public static final int SIZE = 12;

        public boolean fromBytes(byte[] d) throws Exception {
            if (d.length < SIZE) {
                return false;
            }
            this.version = d[0];
            this.service = d[1];
            this.msgtype = ByteUtil.getUnsignedShort(d, 2, ByteOrder.LITTLE_ENDIAN);
            this.topic = ByteUtil.getUnsignedShort(d, 4, ByteOrder.LITTLE_ENDIAN);
            this.optslen = ByteUtil.getUnsignedShort(d, 6, ByteOrder.LITTLE_ENDIAN);
            this.datalen = ByteUtil.getUnsignedInt(d, 8, ByteOrder.LITTLE_ENDIAN);
            return true;
        }

        public byte[] toBytes() {
            byte[] b = new byte[MsgHead.SIZE];
            ByteUtil.setByte(b, 0, this.version);
            ByteUtil.setByte(b, 1, this.service);
            ByteUtil.setShort(b, 2, this.msgtype, ByteOrder.LITTLE_ENDIAN);
            ByteUtil.setShort(b, 4, this.topic, ByteOrder.LITTLE_ENDIAN);
            ByteUtil.setShort(b, 6, this.optslen, ByteOrder.LITTLE_ENDIAN);
            ByteUtil.setInt(b, 8, this.datalen, ByteOrder.LITTLE_ENDIAN);
            return b;
        }
    };
    // 分页信息
    public class PageInfo {
        int totalCnt;   // 总项目数
        short totalPage;  // 总页数
        short curPage;    // 当前页数
        short pageSize;   // 当前页大小
    };
    // 请求选项
    public class OptionRequest {
        private int optionId = 0;
        private int optionSize = 0;
        private byte[] optionBuf = null;

        public OptionRequest() {
        }

        public OptionRequest(int option_id, int option_size, byte[] option_buf) {
            this.optionId = option_id;
            this.optionSize = option_size;
            this.optionBuf = new byte[option_size];
            System.arraycopy(option_buf, 0, this.optionBuf, 0, option_size);
        }

        public int getOptionId() {
            return optionId;
        }

        public void setOptionId(int optionId) {
            this.optionId = optionId;
        }

        public int getOptionSize() {
            return optionSize;
        }

        public void setOptionSize(int optionSize) {
            this.optionSize = optionSize;
        }

        public byte[] getOptionBuf() {
            return optionBuf;
        }

        public void setOptionBuf(byte[] optionBuf) {
            this.optionBuf = optionBuf;
        }

        public byte[] toBytes() {
            byte[] b = new byte[2 + 2 + this.optionSize];
            ByteUtil.setShort(b, 0, this.optionId, ByteOrder.LITTLE_ENDIAN);
            ByteUtil.setShort(b, 2, this.optionSize, ByteOrder.LITTLE_ENDIAN);
            System.arraycopy(this.optionBuf, 0, b, 4, this.optionSize);
            return b;
        }
    }

    public QtpMessage() {
    }

    public QtpMessage(MsgHead msgHead, byte[] dataBuf, byte[] optBuf) {
        this.msgHead = msgHead;
        this.dataBuf = dataBuf;
        parseOptions(optBuf);
    }

    private void parseOptions(byte[] optBuf) {
        if (optBuf == null) {
            return;
        }
        int offset = 0;
        while (offset < optBuf.length) {
            int optionId = ByteUtil.getUnsignedShort(optBuf, offset, ByteOrder.LITTLE_ENDIAN);
            offset += 2;
            int optionSize = ByteUtil.getUnsignedShort(optBuf, offset, ByteOrder.LITTLE_ENDIAN);
            offset += 2;

            byte[] optionData = new byte[optionSize];
            System.arraycopy(optBuf, offset, optionData, 0, optionSize);
            OptionRequest optionRequest = new OptionRequest(optionId, optionSize, optionData);
            offset += optionSize;

            optionRequestHashMap.put((long) optionId, optionRequest);
        }
    }

    public boolean removeOption(short option) {
        optionRequestHashMap.remove(option);
        return true;
    }

    public boolean addOption(short option, long data) {
        OptionRequest optionRequest = new OptionRequest(option, (short) 8, ByteUtil.long2Bytes(data, ByteOrder.LITTLE_ENDIAN));
        optionRequestHashMap.put((long) option, optionRequest);
        return true;
    }

    public boolean addOption(short option, byte[] data) {
        OptionRequest optionRequest = new OptionRequest(option, (short) data.length, data);
        optionRequestHashMap.put((long) option, optionRequest);
        return true;
    }

    /**
     * 编码器
     * @return
     */
    public byte[] encode() {
        int total = MsgHead.SIZE;
        int totalOptSize = 0;
        int offset = 0;
        // 计算option的总字符长度
        for (Map.Entry<Long, OptionRequest> e : optionRequestHashMap.entrySet()) {
            totalOptSize += e.getValue().getOptionSize() + 2 + 2;
        }
        total += totalOptSize;
        total += dataBuf.length;

        this.msgHead.setOptslen((short) totalOptSize);
        this.msgHead.setDatalen((int) dataBuf.length);
        // 拷贝消息头部
        byte[] b = new byte[total];
        System.arraycopy(this.msgHead.toBytes(), 0, b, offset, MsgHead.SIZE);
        offset += MsgHead.SIZE;
        // 拷贝选项
        for (Map.Entry<Long, OptionRequest> e : optionRequestHashMap.entrySet()) {
            byte[] optBuf = e.getValue().toBytes();
            System.arraycopy(optBuf, 0, b, offset, optBuf.length);
            offset += optBuf.length;
        }
        // 拷贝数据内容
        System.arraycopy(dataBuf, 0, b, offset, dataBuf.length);
        offset += dataBuf.length;
        return b;
    }

    /**
     * 解码器
     * @param b
     * @return
     */
    public boolean decode(byte[] b) {
        // 先解析MsgHead
        MsgHead msgHead = new MsgHead();
        try {
            // 解析头部
            msgHead.fromBytes(b);

            // 解析选项
            byte[] optBuf = new byte[msgHead.optslen];
            System.arraycopy(b, msgHead.SIZE, optBuf, 0, msgHead.optslen);
            parseOptions(optBuf);

            // 解析数据报文
            dataBuf = new byte[(int) msgHead.datalen];
            System.arraycopy(b, msgHead.SIZE + msgHead.optslen, dataBuf, 0, (int) msgHead.datalen);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return true;
    }

    public MsgHead getMsgHead() {
        return msgHead;
    }

    public void setMsgHead(MsgHead msgHead) {
        this.msgHead = msgHead;
    }

    public byte[] getData() {
        return dataBuf;
    }
    public boolean setData(byte[] data) {
        this.dataBuf = new byte[data.length];
        System.arraycopy(data, 0, this.dataBuf, 0, data.length);
        return true;
    }

    public OptionRequest getOption(short option) {
        return optionRequestHashMap.get(option);
    }

    private MsgHead msgHead = new MsgHead();
    Map<Long, OptionRequest> optionRequestHashMap = new HashMap<Long, OptionRequest>();
    byte[] dataBuf = null;
}
